package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.to.OrderTo;
import com.atguigu.common.to.StockDetailTo;
import com.atguigu.common.to.StockLockedTo;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.feign.OrderFeignService;
import com.atguigu.gulimall.feign.ProductFeignService;
import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.service.WareSkuService;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.OrderVo;
import com.atguigu.gulimall.ware.vo.SkuHasStockVo;
import com.atguigu.gulimall.ware.vo.WareSkuLockVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired

    private RabbitTemplate rabbitTemplate;

    @Autowired
    private WareSkuDao wareSkuDao;

    @Autowired
    private ProductFeignService productFeignService;


    @Autowired
    private OrderFeignService orderFeignService;


    @Autowired
    WareOrderTaskService wareOrderTaskService;

    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<> ();
        String skuId = (String) params.get ("skuId");
        if (!StringUtils.isEmpty (skuId)) {
            wrapper.eq ("sku_id", skuId);
        }

        String wareId = (String) params.get ("wareId");
        if (!StringUtils.isEmpty (wareId)) {
            wrapper.eq ("ware_id", wareId);
        }

        IPage<WareSkuEntity> page = this.page (
                new Query<WareSkuEntity> ().getPage (params),
                wrapper
        );
        return new PageUtils (page);
    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        //1、判断如果还没有这个库存记录新增
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList (new QueryWrapper<WareSkuEntity> ().eq ("sku_id", skuId).eq ("ware_id", wareId));
        if (wareSkuEntities == null || wareSkuEntities.size () == 0) {
            WareSkuEntity entity = new WareSkuEntity ();
            entity.setSkuId (skuId);
            entity.setStock (skuNum);
            entity.setWareId (wareId);
            entity.setStockLocked (0);
            //远程查询SKU的名字、如果失败、整个事务无需回滚
            try {
                R info = productFeignService.info (skuId);
                Map<String, Object> skuInfo = (Map<String, Object>) info.get ("skuInfo");
                if (info.getCode () == 0) {
                    entity.setSkuName ((String) skuInfo.get ("skuName"));
                }
            } catch (Exception e) {

            }

            wareSkuDao.insert (entity);

        }
        wareSkuDao.addStock (skuId, wareId, skuNum);
    }


    /**
     * 查询当前Sku的总库存量
     *
     * @param skuIds
     * @return
     */
    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {

        List<SkuHasStockVo> collect = skuIds.stream ().map (skuId -> {
            SkuHasStockVo vo = new SkuHasStockVo ();
            //查询当前Sku的总库存量
            //SELECT SUM(stock - stock_locked) FROM wms_ware_sku WHERE sku_id=1
            Long count = baseMapper.getSkuStock (skuId);
            vo.setSkuId (skuId);
            vo.setHasStock (count == null ? false : count > 0);   //count 是空的肯定没有库存
            return vo;
        }).collect (Collectors.toList ());
        return collect;
    }

    /**
     * 为某个订单锁定库存
     * <p>
     * 只要失败、是抛异常的失败
     * 一抛异常、假设前面两个都锁成功了、最后一个失败了、前面还会随着回滚
     * 默认运行时异常都会回滚
     * <p>
     * 库存解锁场景
     * 1)、下单成功，订单过期没用支付被系统自动取消，被用户手动取消。都要解锁库存
     * <p>
     * 2)、下订单成功，库存锁定成功，接下来的业务调用失败、导致订单回滚。
     * 之前锁定的库存就要自动解锁
     *
     * @param vo
     * @return
     */
    @Transactional
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {

        /**
         * 保存库存工作单的详情
         * 后期追溯。
         */

        WareOrderTaskEntity taskEntity = new WareOrderTaskEntity ();
        taskEntity.setOrderSn (vo.getOrderSn ()); //保存订单号
        wareOrderTaskService.save (taskEntity);

        //1、按照下单的收货地址，找到一个就近的仓库，锁定库存
        //1、找到每个商品在哪个库存都有库存
        List<OrderItemVo> locks = vo.getLocks ();

        List<SkuWareHasStock> collect = locks.stream ().map (item -> {
            SkuWareHasStock stock = new SkuWareHasStock ();
            //先来找一下这个商品在哪里都有库存
            Long skuId = item.getSkuId ();
            stock.setSkuId (skuId);
            //数量
            stock.setNum (item.getCount ());
            //查询这个商品在哪里有库存
            List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock (skuId);
            stock.setWareId (wareIds);
            return stock;
        }).collect (Collectors.toList ());

        Boolean allLock = true; //锁库存标志
        //2、锁定库存
        for (SkuWareHasStock hasStock : collect) {

            Boolean skuStocked = false;  //锁那个商品的标识
            Long skuId = hasStock.getSkuId ();
            //在这个商品有库存->从这些仓库挨个扣、那个口成功了、跳出扣下一个sku
            List<Long> wareIds = hasStock.getWareId ();

            if (wareIds == null && wareIds.size () < 0) {
                //没用任何仓库有这个商品库存
                throw new NoStockException (skuId);
            }

            //1、如果每一个商品锁定成功，将当前商品锁定了几件的工作单记录发给MQ
            //2、如果锁定失败。前面保存的工作单信息就回滚了。发出去的消息，即使要解锁记录，由于取数据库查不到id、所以就不用解锁
            for (Long wareId : wareIds) {
                //成功就返回1、否则就是0
                Long count = wareSkuDao.lockSkuStock (skuId, wareId, hasStock.getNum ());
                if (count == 1) {
                    ////锁成功
                    skuStocked = true;

                    //只要锁成功了就保存锁成功的详情
                    WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity (null, skuId, "", hasStock.getNum (), taskEntity.getId (), wareId, 1);
                    wareOrderTaskDetailService.save (entity);

                    //只要锁定成功就会给mq发送一个消息
                    StockLockedTo lockedTo = new StockLockedTo ();
                    lockedTo.setId (taskEntity.getId ());

                    StockDetailTo stockDetailTo = new StockDetailTo ();
                    BeanUtils.copyProperties (entity, stockDetailTo);
                    //只发id不行，防止回滚以后找不到数据
                    lockedTo.setDetailId (stockDetailTo);
                    //下单成功发消息---加锁
                    rabbitTemplate.convertAndSend ("stock-event-exchange", "stock.locked", lockedTo);
                    System.out.println ("下单发送消息成功");
                    break;
                } else {
                    //当前仓库锁失败、重试下一个仓库
                }
            }

            if (skuStocked == false) {
                //当前商品所有仓库都没有锁住
                throw new NoStockException (skuId);
            }

        }
        //3、走到这都是全部锁定成功的、要成都成、反之、只要不return true都是失败的
        return true;
    }

    @Override
    public void unLockStock(StockLockedTo to) {

        System.out.println ("收到解锁库存的消息");

        StockDetailTo detail = to.getDetailId ();
        Long detailId = detail.getId ();
        //解锁
        //1、查询数据库关于这个订单的锁定库存信息
        //有 :证明库存锁定成功了
//                解锁：订单情况。
        //1、没用这个订单。必须解锁
        //2、有这个订单。不是解锁库存
        //订单状态：已取消：解锁库存
//                            没取消：不能解锁
        //没有：库存锁定失败了、库存回滚了、这种情况无需回滚
        WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById (detailId);
        if (byId != null) {
            //解锁
            Long id = to.getId (); //库存工作id
            WareOrderTaskEntity taskEntity = wareOrderTaskService.getById (id);
            String orderSn = taskEntity.getOrderSn (); //根据订单号查询订单状态

            R r = orderFeignService.getOrderStatus (orderSn);
            if (r.getCode () == 0) {
                //订单数据返回成功
                OrderVo dta = r.getDta (new TypeReference<OrderVo> () {
                });

                //消息库存拒绝以后继续返回队列、重构的逻辑
                if (dta == null || dta.getStatus () == 4) {
                    //订单不存在
                    //订单已被取消，才能解锁库存
                    //被人解锁了就不解锁了
                    if (byId.getLockStatus () == 1) {
                        System.out.println ("解锁的参数：detail.getSkuId ()" + detail.getSkuId () + "---detail.getWareId ():" + detail.getWareId () + "----detail.getSkuNum ()" + detail.getSkuNum () + "--detailId" + detailId);
                        //当前库存工作单详情，状态1 已锁定但是未解锁才可以解锁
                        unLockStock (detail.getSkuId (), detail.getWareId (), detail.getSkuNum (), detailId);
                    }

                } else {
                    //消息拒绝以后重新放回队列里面 ，让别人继续消费解锁
                    throw new RuntimeException ("远程调用失败");
                }
            }
        } else {
            //无需解锁

        }

    }

    /**
     * 订单解锁
     * 防止订单服务卡顿，导致订单状态消息一直改不了、库存消息优先到期。查订单状态新建状态，什么都不做就走了
     * 导致卡顿的订单，永远不能解锁库存
     *
     * @param orderTo
     */

    @Transactional
    @Override
    public void unLockStock(OrderTo orderTo) {

        String orderSn = orderTo.getOrderSn ();
        //查一下最新库存状态，防止重复解锁库存、根据他的id能找到每一个商品被锁定什么样
        WareOrderTaskEntity task = wareOrderTaskService.getOrderTasByOrderSn (orderSn);

        Long id = task.getId ();

        //按照工作单 找到所有  没用解锁库存、
        List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list (
                new QueryWrapper<WareOrderTaskDetailEntity> ()
                        .eq ("task_id", id) //库存工作单id
                        .eq ("lock_status", 1) //1、新建状态、还未解锁
        );

        //Long skuId,Long wareId,Integer num,Long taskDetailId
        for (WareOrderTaskDetailEntity entity : list) {
            //解锁 entity.getWareId ()-》那个仓库解锁     entity.getSkuNum ()-》解锁几件    entity.getId ()-》工作单的详情id
            unLockStock (entity.getSkuId (), entity.getWareId (), entity.getSkuNum (), entity.getId ());
        }

    }


    /**
     * 1、库存自动解锁。
     * 下单成功，库存锁定成功，接下来的业务调用失败、导致订单回滚、之前锁定的库存就要自动解锁
     * 2、订单失败。
     * 锁库存失败
     * <p>
     * 只要解锁库存的消息失败、一定要告诉服务解锁失败
     *
     * @param skuId
     * @param wareId
     * @param num
     * @param taskDetailId
     */
    private void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {
        wareSkuDao.unLockStock (skuId, wareId, num, taskDetailId);

        //解锁成功修改解锁状态
        WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity ();
        entity.setId (taskDetailId);
        entity.setLockStatus (2); //变为已解锁
        wareOrderTaskDetailService.updateById (entity);
    }
}

@Data
class SkuWareHasStock {
    private Long skuId;
    private Integer num;
    private List<Long> wareId;


    /**
     *   System.out.println ("收到解锁库存的消息");
     *
     *         StockDetailTo detail = to.getDetailId ();
     *         Long detailId = detail.getSkuId ();
     *         //解锁
     *         //1、查询数据库关于这个订单的锁定库存信息
     *         //有 :证明库存锁定成功了
     * //                解锁：订单情况。
     *         //1、没用这个订单。必须解锁
     *         //2、有这个订单。不是解锁库存
     *         //订单状态：已取消：解锁库存
     * //                            没取消：不能解锁
     *         //没有：库存锁定失败了、库存回滚了、这种情况无需回滚
     *         WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById (detailId);
     *         if (byId != null) {
     *             //解锁
     *             Long id = to.getId (); //库存工作id
     *             WareOrderTaskEntity taskEntity = wareOrderTaskService.getById (id);
     *             String orderSn = taskEntity.getOrderSn (); //根据订单号查询订单状态
     *
     *             R r = orderFeignService.getOrderStatus (orderSn);
     *             if (r.getCode () == 0) {
     *                 //订单数据返回成功
     *                 OrderVo dta = r.getDta (new TypeReference<OrderVo> () {
     *                 });
     *
     *                 //消息库存拒绝以后继续返回队列、重构的逻辑
     *                 if (dta == null || dta.getStatus () == 4) {
     *                     //订单不存在
     *                     //订单已被取消，才能解锁库存
     *                     unLockStock (detail.getSkuId (), detail.getWareId (), detail.getSkuNum (), detailId);
     *                     channel.basicAck (message.getMessageProperties ().getDeliveryTag (), false);
     *                 }else {
     *                     //消息拒绝以后重新放回队列里面 ，让别人继续消费解锁
     *                     channel.basicReject (message.getMessageProperties ().getDeliveryTag (),true);
     *                 }
     *             }
     *         } else {
     * //无需解锁
     *             channel.basicAck (message.getMessageProperties ().getDeliveryTag (), false);
     *         }
     */
}



